package com.glsc.ngateway.ldap.service;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.glsc.ngateway.common.api.common.enums.GatewayConstant;
import com.glsc.ngateway.common.api.common.utils.DateUtil;
import com.glsc.ngateway.common.api.common.utils.GatewayStrUtil;
import com.glsc.ngateway.common.api.ldap.dto.LdapEhrEmpDto;
import com.glsc.ngateway.common.api.ldap.dto.LdapWorkWechatTokenCheckResultDto;
import com.glsc.ngateway.common.api.ldap.dto.OaParamDto;
import com.glsc.ngateway.common.api.ldap.dto.TokenMapDto;
import com.glsc.ngateway.common.api.platform.dto.system.SystemMainDto;
import com.glsc.ngateway.common.api.platform.dto.user.PlatformUserDto;
import com.glsc.ngateway.common.api.platform.feign.IFeignPlatformService;
import com.glsc.ngateway.common.api.platform.resp.PlatformResponse;
import com.glsc.ngateway.common.base.domain.mysql.gateway.ldap.LdapEhrComp;
import com.glsc.ngateway.common.base.domain.mysql.gateway.ldap.UserVisitFlow;
import com.glsc.ngateway.common.base.domain.mysql.gateway.other.SystemConfig;
import com.glsc.ngateway.common.base.domain.mysql.gateway.platform.User;
import com.glsc.ngateway.common.base.enums.UserTypeEnum;
import com.glsc.ngateway.common.base.repo.mysql.gateway.other.SystemConfigRepository;
import com.glsc.ngateway.common.base.utils.OaAESEncryptUtil;
import com.glsc.ngateway.common.base.utils.redis.IbltRedis;
import com.glsc.ngateway.ldap.common.constant.LdapConstant;
import com.glsc.ngateway.ldap.enums.CheckLoginStatuEnum;
import com.glsc.ngateway.ldap.utils.JwtHelper;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.gson.JsonObject;
import com.netflix.discovery.shared.Pair;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.lang.Nullable;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.http.HttpRequest;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * 负责登录检查、校验权限等
 */
@Service
public class LdapLoginService {


    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private LdapTemplate ldapTemplate;

    @Resource
    private LdapUserFlowService ldapUserFlowService;

    @Autowired
    private IbltRedis ibltRedis;

    @Autowired
    private IFeignPlatformService iFeignPlatformService;

    @Resource
    private LdapEhrEmpService ehrEmpService;

    @Resource
    private LdapEhrDeptService ehrDeptService;

    @Resource
    private LdapEhrCompService ehrCompService;

    @Resource
    private SystemConfigRepository systemConfigRepo;

    @Value("${oaTokenUrl}")
    String oaTokenUrl;

    @Value("${secret}")
    String secret;

    @Value("${oaTokenFlag}")
    private String oaTokenFlag;

    @Value("${workWechat.token-request-url}")
    private String workWechatTokenRequestUrl;

    public boolean checkOaUser(String userId) {
        return Objects.nonNull(ehrEmpService.findByOa(userId));
    }


    public CheckLoginStatuEnum checkLogin(@NotBlank String userName, @NotBlank String password) {
        CheckLoginStatuEnum authorityResult;
        //特殊用户处理
        if (needLoginByPlatformUser(userName)) {
            authorityResult = checkPlatformUser(userName, password);
        } else {
            authorityResult = checkLdapUser(userName, password);
        }
        return authorityResult;
    }

    public CheckLoginStatuEnum checkLdapUser(String userName, String password) {
        logger.info("认证用户,调用时间为：" + System.currentTimeMillis());
        EqualsFilter filter = new EqualsFilter(GatewayConstant.LDAP_OA_USR, userName);
        Boolean auFlag = ldapTemplate.authenticate(GatewayConstant.LDAP_OA, filter.toString(), password);
        logger.info("认证用户,认证用户状态为：" + auFlag + ",完成时间为：" + System.currentTimeMillis());
        //如果校验不通过
        if (!auFlag) {
            //判断用户名是否存在，提示用户名错误
            return ehrEmpService.checkUser(userName);
        } else if (ldapTemplate.search(GatewayConstant.LDAP_OA,
                new EqualsFilter(GatewayConstant.LDAP_OA_USR, userName).encode(),
                (AttributesMapper<String>) attrs -> (String) attrs.get("sAMAccountName").get()).stream().noneMatch(n -> n.equals(userName))) {
            //二次检查区分大小写
            return CheckLoginStatuEnum.USER_NOT_EXIST;
        }
        return CheckLoginStatuEnum.SUCCESS;
    }

    public CheckLoginStatuEnum checkPlatformUser(String userName, String password) {
        logger.info("认证用户platformToken,{},调用时间为：{}", userName, System.currentTimeMillis());
        PlatformUserDto user = iFeignPlatformService.findByAccount(userName).getData();
        if (user != null && StringUtils.isNotEmpty(user.getPassword())) {
            if (user.getPassword().equalsIgnoreCase(password)) {
                logger.info("查询用户状态,完成时间为：" + System.currentTimeMillis());
                //正确
                return CheckLoginStatuEnum.SUCCESS;
            } else {
                //用户错误
                return CheckLoginStatuEnum.PASSWORD_WRONG;
            }
        } else {
            //错误
            return CheckLoginStatuEnum.USER_NOT_EXIST;
        }
    }

    public CheckLoginStatuEnum checkLoginPortalForOa(OaParamDto oaParamDto) throws Exception {
        logger.info("认证用户,{},{},调用时间为：{}", oaParamDto.getUserName(), oaParamDto.getVaildCode(), System.currentTimeMillis());
        //旧逻辑
        //otoken=密钥+loginid+yyyy-mm-dd的时间戳，限定当日的才能通过
        String validCode = DigestUtils.md5DigestAsHex((secret + oaParamDto.getUserName() + LocalDate.now().toString()).getBytes());
        logger.info("认证用户oaToken,认证用户校验码为：{},完成时间为：", validCode, System.currentTimeMillis());
        CheckLoginStatuEnum status;
        if (!oaParamDto.getVaildCode().equalsIgnoreCase(validCode)) {
            status = CheckLoginStatuEnum.VERIFICATION_CODE_WRONG;
        } else if (ehrEmpService.ldapContainsUserName(oaParamDto.getUserName())) {
            status = CheckLoginStatuEnum.SUCCESS;
        } else {
            status = CheckLoginStatuEnum.USER_NOT_EXIST;
        }
        //新逻辑
        if (status != CheckLoginStatuEnum.SUCCESS) {
            validCode = OaAESEncryptUtil.decode(oaParamDto.getVaildCode());
            logger.info("认证用户oaToken,进行oaToken解密，解密前{}, 解密后{} ,完成时间为：", oaParamDto.getVaildCode(), validCode, System.currentTimeMillis());
            if (Strings.isBlank(validCode)) {
                status = CheckLoginStatuEnum.VERIFICATION_CODE_WRONG;
            } else {
                SystemConfig byConfigCode = systemConfigRepo.findByConfigCode(OaAESEncryptUtil.CONFIG_OA_SECURE_KEY_SPLIT);
                if (Objects.isNull(byConfigCode)) {
                    throw new Exception("缺少配置" + OaAESEncryptUtil.CONFIG_OA_SECURE_KEY_SPLIT);
                }
                String[] split = validCode.split(byConfigCode.getConfigValue());
                if (split.length < 3 || !split[2].equals(LocalDate.now().toString())) {
                    status = CheckLoginStatuEnum.VERIFICATION_CODE_WRONG;
                } else {
                    oaParamDto.setUserName(split[0]);
//                    if (ehrEmpService.ldapContainsUserName(oaParamDto.getUserName())) {
                    status = CheckLoginStatuEnum.SUCCESS;
//                    } else {
//                        status = CheckLoginStatuEnum.USER_NOT_EXIST;
//                    }
                }
            }
        }
        logger.info("验证完成,{},完成时间为：{}", status, System.currentTimeMillis());
        return status;
    }

    /**
     * 判断是否需要使用网关库里的密码进行校验
     */
    public boolean needLoginByPlatformUser(String userName) {
        boolean flag = false;
        User user = ehrEmpService.getPlatformUser(userName);
        if (Objects.nonNull(user) && StringUtils.isNotBlank(user.getUserType())) {
            UserTypeEnum userType = UserTypeEnum.valuesOf(user.getUserType());
            //系统用户和外包人员使用网关t_user存的密码登录
            flag = userType.isSysUser() || userType.isLaborDispatch();
        }
        return flag;
    }

    public TokenMapDto getTokenMap(String account, String systemId) {
        TokenMapDto tokenMap = getUserToken(account, systemId);
        String userDeptId = ehrDeptService.getSystemIdByUser(account);
        //增加用户所属一级部门字段
        String department = "";
        if (account.equalsIgnoreCase(LdapConstant.itadmin)) {
            department = LdapConstant.信息技术总部;
        } else {
            department = ehrDeptService.getUserDepartment(account, needLoginByPlatformUser(account));
        }
        logger.info("用户-{}，所属部门-{}", account, department);

        if (Strings.isNotBlank(department)) {
            logger.info("用户" + account + "所属部门:" + department);
            tokenMap.setDepartment(department);
            LdapEhrComp comp = ehrCompService.findByEmpAccount(account);
            String sysid = ehrDeptService.getSystemIdByDepartment(department, comp == null?null:comp.getSubcompanyname() );
            logger.info("第一次获取{}-sysid:{}", department, sysid);
            //如果根据部门未找到系统，再根据公司找一次
            if (Strings.isBlank(sysid)) {
//                LdapEhrComp comp = ehrCompService.findByEmpAccount(account);
                if (Objects.nonNull(comp)) {
                    sysid = ehrDeptService.getSystemIdByDepartment(comp.getSubcompanyname(), comp == null?null:comp.getSubcompanyname() );
                    logger.info("第二次获取{}-sysid:{}", comp, sysid);
                }
            }
            //以用户的配置优先
            if (StrUtil.isNotEmpty(userDeptId)){
                tokenMap.setSystemId(userDeptId);
            } else {
                tokenMap.setSystemId(Strings.isBlank(sysid) ? "oaecology" : sysid);
            }
            //排序，并获取systemid->系统编号（前端排序，后端不调用接口）
//            Pair<String, Map<String, String>> pair = orderSys(tokenMap.getSystemId());
//            tokenMap.setSystemId(pair.first());
//            tokenMap.setSystemInfoMap(pair.second());
            //获取中文名称
            String chineseName = ehrEmpService.getUserChineseName(account);
            tokenMap.setChineseName(chineseName);
            tokenMap.setUsername(account);
            if ("Y".equalsIgnoreCase(oaTokenFlag)) {
                tokenMap.setOaToken(getOaToken(account));
                logger.info("oaToken:" + tokenMap.getToken());
            }
            //获取用户头像
            LdapEhrEmpDto emp = ehrEmpService.findByBadge(account);
            tokenMap.setMessagerUrl(Objects.nonNull(emp) ? emp.getMessagerUrl() : Strings.EMPTY);
        }
        logger.info("获取用户token，用户名=" + account + ",时间=" + DateUtil.getCurrentDate(DateUtil.formatMillisecond));
        return tokenMap;
    }

    public TokenMapDto getUserToken(String userName, String systemId) {
        TokenMapDto tokenMap = new TokenMapDto();
        String token = JwtHelper.generateToken(userName);
        // 方便根据 token 查询用户
        ibltRedis.set(GatewayConstant.PREFIX_KEY_TOKEN + token, userName);
        UserVisitFlow uvf = new UserVisitFlow(systemId, userName, token);
        ldapUserFlowService.save(uvf);
        tokenMap.setToken(token);
        tokenMap.setExpireTime(uvf.getExpireTime().toString());
        return tokenMap;
    }

    public Map<String, String> checkTokenValid(String token, String systemId) throws Exception {
        return checkTokenValid(token, systemId, null);
    }

    public Map<String, String> checkTokenValid(String token, String systemId, @Nullable String username) throws Exception {
        Map<String, String> dataMap = new HashMap<>();
        //  String  redisUserName =ibltRedis.getValueByKey(GatewayConstant.PREFIX_KEY_TOKEN+systemId+":"+token);
        String realUserName = null;
        String redisUserName = ibltRedis.getValueByKey(GatewayConstant.PREFIX_KEY_TOKEN + token);
        //从缓存中找到
        if (Strings.isNotBlank(redisUserName)) {
            realUserName = redisUserName;
            //   String expireTime  = ibltRedis.getExpireTime(GatewayConstant.PREFIX_KEY_TOKEN+systemId+":"+token);
            String expireTime = ibltRedis.getExpireTime(GatewayConstant.PREFIX_KEY_TOKEN + token);
            long endTimeStamp = Long.valueOf(expireTime) * 1000;
            long nowTimeStamp = System.currentTimeMillis();
            logger.info("endTimeStamp:" + endTimeStamp + "  nowTimeStamp:" + nowTimeStamp);
            //小于半小时则续期
            if ((endTimeStamp - nowTimeStamp) < 1000 * 60 * 60 * 0.5) {
                this.delayExpireTime(token, 2, systemId);
                expireTime = ibltRedis.getExpireTime(GatewayConstant.PREFIX_KEY_TOKEN + token);
            }
            dataMap.put(GatewayConstant.TOKEN_EXPIRE_TIME, expireTime);
        }
        //网关token
        if (Strings.isBlank(realUserName)) {
            realUserName = JwtHelper.parseToken(token);
        }
        //兼容oaToken
        if (Strings.isBlank(realUserName)) {
            OaParamDto oaCkeckParam = OaParamDto.builder().vaildCode(token).build();
            CheckLoginStatuEnum checkOaRes = checkLoginPortalForOa(oaCkeckParam);
            if (CheckLoginStatuEnum.SUCCESS == checkOaRes) {
                realUserName = oaCkeckParam.getUserName();
            } else {
                logger.info("用户{}校验oaToken失败", username);
            }
        }

        //兼容企微token
        if (Strings.isBlank(realUserName) && StrUtil.isNotEmpty(token) && token.length() > 200) {
            logger.info("超长token，尝试企微检查，网关生成的代码不超过200个字符");
            LdapWorkWechatTokenCheckResultDto wechatTokenCheckResultDto = this.checkWorkWechatToken(token);
            if (wechatTokenCheckResultDto != null) {
                realUserName = wechatTokenCheckResultDto.getOa_account();
            } else {
                logger.info("用户{}校验企微Token失败:{}", username);
            }
        }

        if (Strings.isBlank(realUserName)) {
            dataMap.put(GatewayConstant.LDAP_USER_NAME, GatewayConstant.TOKEN_OVERDUE); //
            Long expireTime = ibltRedis.getExireTimeStamp(GatewayConstant.PREFIX_KEY_TOKEN + token);
            logger.info("用户" + redisUserName + "，" + (expireTime == -2 ?
                    "token不存在" : (expireTime == -1 ?
                    "token无过期时间" : new Date(expireTime + System.currentTimeMillis()))
            ));
        } else {
            dataMap.put(GatewayConstant.LDAP_USER_NAME, realUserName);
            //加载部门信息等
            //获取中文名称
            String department = ehrDeptService.getUserDepartment(realUserName, needLoginByPlatformUser(realUserName));
            if (Strings.isNotBlank(department)) {
                logger.info("用户" + realUserName + "所属部门:" + department);
                dataMap.put("department", department);
                String chineseName = ehrEmpService.getUserChineseName(realUserName);
                dataMap.put("chineseName", chineseName);
            }
        }
        return dataMap;
    }

    public Boolean kickoutToken(String token, String systemId) {
//        Boolean flag = false;
        Boolean flag = ibltRedis.deleteByKey(GatewayConstant.PREFIX_KEY_TOKEN + token);
        return flag;
    }

    public void delayExpireTime(String token, Integer delayHours, String systemId) {
        ibltRedis.setExpire(GatewayConstant.PREFIX_KEY_TOKEN + token, delayHours);
        String expireTime = ibltRedis.getExpireTime(GatewayConstant.PREFIX_KEY_TOKEN + token);
        ldapUserFlowService.delayExpireTime(Long.parseLong(expireTime), token);
    }

    public String getExpireTime(String token) {
        String expireTime = ibltRedis.getExpireTime(GatewayConstant.PREFIX_KEY_TOKEN + token);
        return expireTime;
    }


    /**
     * 获取oa权限
     *
     * @param userName
     * @return
     */
    public String getOaToken(String userName) {
        String url = oaTokenUrl;
        Map<String, String> params = new HashMap<>();
        params.put("appid", "Client1");
        params.put("loginid", userName);
        BufferedReader in = null;
        try {
            // 定义 HttpClient
            HttpClient client = new DefaultHttpClient();
            // 实例化 HTTP 方法
            HttpPost request = new HttpPost();
            request.setURI(new URI(url));
            //设置参数
            List<NameValuePair> nvps = new ArrayList<NameValuePair>();
            for (Iterator iter = params.keySet().iterator(); iter.hasNext(); ) {
                String name = (String) iter.next();
                String value = String.valueOf(params.get(name));
                nvps.add(new BasicNameValuePair(name, value));
                //System.out.println(name +"-"+value);
            }
            request.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
            logger.info("获取oaToken请求信息：" + JSON.toJSONString(request));
            HttpResponse response = client.execute(request);
            int code = response.getStatusLine().getStatusCode();
            if (code == 200) { //请求成功
                in = new BufferedReader(new InputStreamReader(response.getEntity()
                        .getContent(), "utf-8"));
                StringBuffer sb = new StringBuffer("");
                String line = "";
//                String NL = System.getProperty("line.separator");
                while ((line = in.readLine()) != null) {
                    sb.append(line);
                }
                in.close();
                return sb.toString();
            } else { //
                System.out.println("状态码： " + code);
                return null;
            }

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据系统编号进行排序，返回Pair<排序后的字符串，Map<systemId，系统编号>>
     *
     * @param sysid
     * @return
     */
    private Pair<String, Map<String, String>> orderSys(String sysid) {
        //对查询sysid编号，并根据编号进行排序
        List<String> sysIds = Arrays.stream(sysid.split(",")).collect(Collectors.toList());
        PlatformResponse<List<SystemMainDto>> resp = iFeignPlatformService.findAllBySystemIdIn(sysIds);
        Map<String, String> sysMainMap = resp.getData().stream().collect(Collectors.toMap(SystemMainDto::getSystemId, SystemMainDto::getSystemClassNo, (o1, o2) -> o1, LinkedHashMap::new));
        sysIds = sysIds.stream()
                .sorted((s1, s2) -> {
                    String sys1 = sysMainMap.get(s1);
                    String sys2 = sysMainMap.get(s2);
                    sysMainMap.putIfAbsent(s1, Strings.EMPTY);
                    sysMainMap.putIfAbsent(s2, Strings.EMPTY);
                    if (Objects.nonNull(sys1) && Objects.nonNull(sys2)) {
                        //都有编号比较编号
                        return GatewayStrUtil.sequenceCompare(sys1, sys2);
                    } else if (Objects.isNull(sys1) && Objects.isNull(sys2)) {
                        //都无编号，比较字典序
                        return s1.compareTo(s2);
                    } else {
                        //其中一个有，有的小，排在前
                        return Objects.isNull(sys1) ? 1 : -1;
                    }
                })
                .collect(Collectors.toList());
        return new Pair<>(String.join(",", sysIds), sysMainMap);
    }

    /**
     * 企微token本地缓存
     * * *
     */
    Cache<String, JSONObject> wxTokenCache = CacheBuilder.newBuilder()
            // 设置缓存的最大容量
            .maximumSize(1000)
            // 设置缓存项的过期时间（可选）
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .build();

    /**
     * 财富接口服务，校验企业微信token有效性
     * * *
     */
    private java.net.http.HttpResponse<String> getStringHttpResponse(String token) {
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("token", token);
        HttpRequest workWechatRequest = HttpRequest.newBuilder()
                .uri(URI.create(workWechatTokenRequestUrl))
                .method("POST", HttpRequest.BodyPublishers.ofString(jsonObject.toString()))
                .header("Accept", "*/*")
                .header("Content-Type", "application/json")
                .build();
        java.net.http.HttpClient client = java.net.http.HttpClient.newHttpClient();
        java.net.http.HttpResponse<String> workWechatResp = null;
        try {
            workWechatResp = client.send(workWechatRequest, java.net.http.HttpResponse.BodyHandlers.ofString());
        } catch (IOException  e) {
            logger.error("企微Token查询异常:{}", e.getMessage(), e);
            return null;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            logger.error("企微Token查询异常:{}", e.getMessage(), e);
            return null;
        }
        logger.info("完成企微Token查询,完成时间为：{}, 结果为{}", System.currentTimeMillis(), workWechatResp);
        return workWechatResp;
    }

    /**
     * 企微token校验，本地缓存降低企微服务调用
     */
    @Nullable
    public LdapWorkWechatTokenCheckResultDto checkWorkWechatToken(@NotBlank String token) {
        JSONObject cacheResp = this.wxTokenCache.getIfPresent(token);
        if (Objects.nonNull(cacheResp)) {
            logger.info("企微token验证获取到缓存{}", cacheResp);
        } else {
            logger.info("企微token验证未获取到缓存，请求接口进行验证");
            java.net.http.HttpResponse<String> resp = getStringHttpResponse(token);
            if (Objects.nonNull(resp)) {
                cacheResp = JSONObject.parseObject(resp.body());
                if (cacheResp.get("code").equals(0)) {
                    logger.info("企微token存入缓存，{}", cacheResp);
                    this.wxTokenCache.put(token, cacheResp);
                } else {
                    logger.info("企微token验证失败，不存入缓存，{}", cacheResp);
                }
            }
        }

        //校验通过
        if (cacheResp.get("code").equals(0)) {
            LdapWorkWechatTokenCheckResultDto resultDto = JSONObject.parseObject(cacheResp.get("data").toString()
                    , LdapWorkWechatTokenCheckResultDto.class);
            return resultDto;
        } else {
            logger.error("企微token校验错误{}", cacheResp.get("msg").toString());
            return null;
        }
    }
}
